/*
 Funções relativas a leitura de arquivo .class
 Funções responsáveis por imprimir o struct do .class na tela
 Funções responsáveis pela leitura do arquivo e conversão para big-endian
 */
#include "classfile.h"
#include "list.h"
#include "bytecode.h"
#include "classfile.h"

extern class_list *methodarea;
extern class_file *mainclass;

class_file *readclass(char *filename);
attribute_info *read_attr(FILE *fd, const u2 size, const cp_info* cp);
void print_attr(const u2 size, const cp_info* cp, const attribute_info *attr,
		const char *align);
u2 u2read(FILE* fd);
u4 u4read(FILE* fd);

void loadClass(char *class_name) {
	class_file *class;
	class = (class_file *) get_class(METHODAREA, class_name);
	/* Carrega a Classe caso ja nao esteja carregada */

	if (!class) {
		class = readclass(class_name);
		insert_node(METHODAREA, class);
	}
}

class_file *get_class(class_list *l, char *class_name) {
	list_node *t = l->first;
	char *class_name_no_ext;
	int size;

	if (!empty_list(l)) {
		for (; t != NULL; t = t->next) {
			/* comparar nomes sem extensao .class */
			/* class_name possui a extensao .class */
			class_name_no_ext = (char*) calloc((strlen(class_name)),
					sizeof(char));
			strcpy(class_name_no_ext, class_name);
			size = strlen(class_name_no_ext);
			class_name_no_ext[size - 6] = '\0'; /* remove .class do nome */
			if (!strcmp(class_name_no_ext, (char*) t->class_name)) {
				break;
			}
			free(class_name_no_ext);
		}
		if (t == NULL) return NULL;
		else return t->class;
	} else return NULL;
	/* retorna NULL em caso de nao achar a classe */
}

class_file *readclass(char *filename) {
	FILE *infile = NULL;
	int i, j;
	class_file *out;

	u1 tag;

	u4 magic_number = 0;
	u2 minor_version = 0;
	u2 major_version = 0;
	u2 constant_pool_count = 0;
	cp_info *constant_pool;
	u2 access_flags = 0;
	u2 this_class = 0;
	u2 super_class = 0;
	u2 interfaces_count = 0;
	u2 *interfaces;
	u2 fields_count = 0;
	field_info *fields;
	u2 methods_count = 0;
	method_info *methods;
	u2 attributes_count = 0;
	attribute_info *attributes;

	infile = fopen(filename, "rb");
	if (!infile) {
		perror(filename);
		exit(1);
	}

	magic_number = u4read(infile);

	if (magic_number != 0xcafebabe) {
		printf("Not a .class file\n");
		exit(1);
	}
	/*Vers�o do .class*/
	minor_version = u2read(infile);
	major_version = u2read(infile);
	/*Tamanho do constant pool*/
	constant_pool_count = u2read(infile);
	/*Alocar espa�o na mem�ria para constant pool*/
	constant_pool = (cp_info*) calloc((constant_pool_count - 1),
			sizeof(cp_info));

	for (i = 0; i < (constant_pool_count - 1); i++) {
		/*Leitura da tag que identifica o tipo da constante*/
		fread(&tag, 1, 1, infile);
		constant_pool[i].tag = tag;
		switch (tag) {
		case 0x01:

			CP_INFO(i).Utf8.lenght = u2read(infile);
			CP_INFO(i).Utf8.bytes = (u1*) calloc(CP_INFO(i).Utf8.lenght + 1,
					sizeof(u1));
			for (j = 0; j < CP_INFO(i).Utf8.lenght; j++) {
				fread((CP_INFO(i).Utf8.bytes + j), 1, 1, infile);
			}
			/*CP_INFO(i).Utf8.bytes[j] = '\0';*/
			break;

		case 0x03:
			CP_INFO(i).Integer.bytes = u4read(infile);
			break;

		case 0x04:
			CP_INFO(i).Float.bytes = u4read(infile);
			break;

		case 0x05:
			/* Long ocupa duas posicoes no constant Pool, � preciso ler dois int e armazenar em dois campos */
			CP_INFO(i).Long.high_bytes = u4read(infile);
			CP_INFO(i).Long.low_bytes = u4read(infile);

			i++;
			break;

		case 0x06:
			/* Double ocupa duas posicoes no constant Pool, � preciso ler dois int e armazenar em dois campos */
			CP_INFO(i).Double.high_bytes = u4read(infile);
			CP_INFO(i).Double.low_bytes = u4read(infile);
			i++;
			break;

		case 0x07:
			CP_INFO(i).Class.name_index = u2read(infile);
			break;

		case 0x08:
			CP_INFO(i).String.string_index = u2read(infile);
			break;

		case 0x09:
			CP_INFO(i).Fieldref.class_index = u2read(infile);
			CP_INFO(i).Fieldref.name_and_type_index = u2read(infile);
			break;

		case 0x0a:
			CP_INFO(i).Methodref.class_index = u2read(infile);
			CP_INFO(i).Methodref.name_and_type_index = u2read(infile);
			break;

		case 0x0b:
			CP_INFO(i).InterfaceMethodref.class_index = u2read(infile);
			CP_INFO(i).InterfaceMethodref.name_and_type_index = u2read(infile);
			break;

		case 0x0c:
			CP_INFO(i).NameAndType.name_index = u2read(infile);
			CP_INFO(i).NameAndType.descriptor_index = u2read(infile);
			break;

		default:
			printf("Tag %x desconhecida (%d);\n", tag, i + 1);
			exit(1);
		}
	}

	access_flags = u2read(infile);
	this_class = u2read(infile);
	super_class = u2read(infile);

	interfaces_count = u2read(infile);
	interfaces = (u2*) calloc(interfaces_count, sizeof(u2));
	for (i = 0; i < interfaces_count; i++) {
		interfaces[i] = u2read(infile);
	}

	fields_count = u2read(infile);
	fields = (field_info*) calloc(fields_count, sizeof(field_info));
	for (i = 0; i < fields_count; i++) {
		fields[i].access_flags = u2read(infile);
		fields[i].name_index = u2read(infile);
		fields[i].descriptor_index = u2read(infile);
		fields[i].attributes_count = u2read(infile);
		fields[i].attributes = (attribute_info*) calloc(
				fields[i].attributes_count, sizeof(attribute_info));
		for (j = 0; j < fields[i].attributes_count; j++) {
			fields[i].attributes[j] = *read_attr(infile,
					fields[i].attributes_count, constant_pool);
		}
	}

	methods_count = u2read(infile);
	methods = (method_info*) calloc(methods_count, sizeof(method_info));
	for (i = 0; i < methods_count; i++) {
		methods[i].access_flags = u2read(infile);
		methods[i].name_index = u2read(infile);
		methods[i].descriptor_index = u2read(infile);
		methods[i].attributes_count = u2read(infile);
		methods[i].attributes = (attribute_info*) calloc(
				methods[i].attributes_count, sizeof(attribute_info));
		for (j = 0; j < methods[i].attributes_count; j++) {
			methods[i].attributes[j] = *read_attr(infile,
					methods[i].attributes_count, constant_pool);
		}
	}

	attributes_count = u2read(infile);
	attributes = (attribute_info*) calloc(attributes_count,
			sizeof(attribute_info));
	for (j = 0; j < attributes_count; j++) {
		attributes[j] = *read_attr(infile, attributes_count, constant_pool);

	}

	fclose(infile);
	out = (class_file*) calloc(1, sizeof(class_file));

	out->magic_number = magic_number;
	out->minor_version = minor_version;
	out->major_version = major_version;
	out->constant_pool_count = constant_pool_count;
	out->constant_pool = constant_pool;
	out->access_flags = access_flags;
	out->this_class = this_class;
	out->super_class = super_class;
	out->interfaces_count = interfaces_count;
	out->interfaces = interfaces;
	out->fields_count = fields_count;
	out->fields = fields;
	out->methods_count = methods_count;
	out->methods = methods;
	out->attributes_count = attributes_count;
	out->attributes = attributes;
	if (VERBOSE) {
		printf("Class version: %d.%d :\n", out->minor_version,
				out->major_version);
		printf("Constants pool: %d \n", out->constant_pool_count);
		printf("Interfaces: %d \n", out->interfaces_count);
		printf("Fields: %d \n", out->fields_count);
		printf("Methods: %d \n", out->methods_count);
		printf("Atributes: %d \n\n", out->attributes_count);
	}
	return out;
	out->class_fields = 0;
}

/* Funcao que realiza a leitura de um campo atributo*/
attribute_info *read_attr(FILE *fd, const u2 size, const cp_info* cp) {
	int i;
	attribute_info *attr = (attribute_info*) calloc(size,
			sizeof(attribute_info));

	if (size == 0) {
		free(attr);
		return NULL;
	}

	attr->attribute_name_index = u2read(fd);
	attr->attribute_lenght = u4read(fd);
	/* Code */
	if (!strcmp((char *) cp[(attr->attribute_name_index) - 1].info.Utf8.bytes,
			"Code")) {
		attr->info.Code.max_stack = u2read(fd);
		attr->info.Code.max_locals = u2read(fd);
		attr->info.Code.code_lenght = u4read(fd);
		attr->info.Code.code = (u1*) calloc((attr->info.Code.code_lenght),
				sizeof(u1));
		for (i = 0; i < (attr->info.Code.code_lenght); i++) {
			fread(&(attr->info.Code.code[i]), 1, 1, fd);
		}

		attr->info.Code.exception_table_lenght = u2read(fd);
		/* atencao */
		attr->info.Code.exception_table = (except_info*) calloc(
				attr->info.Code.exception_table_lenght, sizeof(except_info));

		for (i = 0; i < (attr->info.Code.exception_table_lenght); i++) {
			attr->info.Code.exception_table[i].start_pc = u2read(fd);
			attr->info.Code.exception_table[i].end_pc = u2read(fd);
			attr->info.Code.exception_table[i].handler_pc = u2read(fd);
			attr->info.Code.exception_table[i].catch_type = u2read(fd);
		}

		attr->info.Code.attributes_count = u2read(fd);
		attr->info.Code.attributes = (attribute_info*) calloc(
				attr->info.Code.attributes_count, sizeof(attribute_info));
		for (i = 0; i < (attr->info.Code.attributes_count); i++) {
			attr->info.Code.attributes[i] = *read_attr(fd,
					attr->info.Code.attributes_count, cp);
		}
	}
	/* Exceptions */
	else if (!strcmp(
			(char *) cp[(attr->attribute_name_index) - 1].info.Utf8.bytes,
			"Exceptions")) {
		attr->info.Exceptions.count = u2read(fd);
		attr->info.Exceptions.index_table = (u2*) calloc(
				(attr->info.Exceptions.count), sizeof(u2));

		for (i = 0; i < (attr->info.Exceptions.count); i++) {
			attr->info.Exceptions.index_table[i] = u2read(fd);
		}
	}
	/* ConstantValue */
	else if (!strcmp(
			(char *) cp[(attr->attribute_name_index) - 1].info.Utf8.bytes,
			"ConstantValue")) {
		attr->info.ConstantValue.index = u2read(fd);
	}
	/* Sourcefile */
	else if (!strcmp(
			(char *) cp[(attr->attribute_name_index) - 1].info.Utf8.bytes,
			"SourceFile")) {
		attr->info.Sourcefile.sourcefile_index = u2read(fd);
	}
	/* Outros atributos */
	else {
		for (i = 0; i < (attr->attribute_lenght); i++) {
			fseek(fd, 1L, SEEK_CUR);
		}
	}

	return attr;
}

/* Funcao para leitura de 2 bytes em big endian */
u2 u2read(FILE* fd) {
	u2 u2tmp;
	/* le 2 bytes */
	fread(&u2tmp, 2, 1, fd);


	/* Reajeita a ordem dos bytes para big endian */
	FIX_2(u2tmp);

	return u2tmp;
}

/* Funcao para leitura de 4 bytes em big endian */
u4 u4read(FILE* fd) {
	u4 u4tmp;
	/* le 4 bytes */
	fread(&u4tmp, 4, 1, fd);


	/* reajeita a ordem dos bytes para big endian */
	FIX_4(u4tmp);

	return u4tmp;
}

void printclass(class_file *in) {
	u4 magic_number = in->magic_number;
	u2 minor_version = in->minor_version;
	u2 major_version = in->major_version;
	u2 constant_pool_count = in->constant_pool_count;
	cp_info *constant_pool = in->constant_pool;
	u2 access_flags = in->access_flags;
	u2 this_class = in->this_class;
	u2 super_class = in->super_class;
	u2 interfaces_count = in->interfaces_count;
	u2 *interfaces = in->interfaces;
	u2 fields_count = in->fields_count;
	field_info *fields = in->fields;
	u2 methods_count = in->methods_count;
	method_info *methods = in->methods;
	u2 attributes_count = in->attributes_count;
	attribute_info *attributes = in->attributes;

	int i, j;

	printf("\n--------------------\n\n");
	printf("Arquivo .class lido\n");
	printf("\n--------------------\n\n");
	printf("Magic Number: %X\n", magic_number);
	printf("\n--------------------\n\n");
	printf("Version: %d.%d\n", major_version, minor_version);
	printf("\n--------------------\n\n");
	printf("Constant_pool[%d]\n", constant_pool_count - 1);

	for (i = 0; i < constant_pool_count - 1; i++) {
		printf("\t+ constant_pool[%d]:\n", (i + 1));
		switch (constant_pool[i].tag) {
		case 0x01:
			printf("\t\t- UTF8 - tag: %x\n", constant_pool[i].tag);
			printf("\t\t- tamanho: %d\n", CP_INFO(i).Utf8.lenght);
			printf("\t\t- string: \"");
			for (j = 0; j < CP_INFO(i).Utf8.lenght; j++) {
				printf("%c", (CP_INFO(i).Utf8.bytes[j]));
			}
			printf("\"\n");
			break;

		case 0x03:
			printf("\t\t- Integer - tag: %x\n", constant_pool[i].tag);
			printf("\t\t- Valor: %d\n", CP_INFO(i).Integer.bytes);
			break;

		case 0x04:
			printf("\t\t- Float - tag: %x\n", constant_pool[i].tag);
			printf("\t\t- Valor: %d\n", CP_INFO(i).Float.bytes);
			break;

		case 0x05:
			printf("\t\t- Long - tag: %x\n", constant_pool[i].tag);
			printf("\t\t- High Bytes: 0x%08x\n", CP_INFO(i).Long.high_bytes);
			printf("\t\t- Low Bytes: 0x%08x\n", CP_INFO(i).Long.low_bytes);
			printf("\t\t- Valor: %ld\n",
					(long) ((long) (CP_INFO(i).Long.high_bytes << 8)
							| (long) CP_INFO(i).Long.low_bytes));
			i++;
			printf("\t+ constant_pool[%d]:\n", (i + 1));
			printf("\t\t- Large Numeric Continued\n");
			break;

		case 0x06:
			printf("\t\t- Double - tag: %x\n", constant_pool[i].tag);
			printf("\t\t- High Bytes: 0x%08x\n", CP_INFO(i).Double.high_bytes);
			printf("\t\t- Low Bytes: 0x%08x\n", CP_INFO(i).Double.low_bytes);
			printf("\t\t- Valor: %f\n", (double) ((CP_INFO(i).Double.high_bytes
					<< 8) | CP_INFO(i).Double.low_bytes));
			i++;
			printf("\t+ constant_pool[%d]:\n", (i + 1));
			printf("\t\t- Large Numeric Continued\n");
			break;

		case 0x07:
			printf("\t\t- Ref. Classe - tag: %x\n", constant_pool[i].tag);
			printf("\t\t- indice: %d\n", CP_INFO(i).Class.name_index);

			break;

		case 0x08:
			printf("\t\t- String - tag: %x\n", constant_pool[i].tag);
			printf("\t\t- indice: %d\n", CP_INFO(i).String.string_index);
			break;

		case 0x09:
			printf("\t\t- Ref. Field - tag: %x\n", constant_pool[i].tag);
			printf("\t\t- indice de classe: %d\n",
					CP_INFO(i).Fieldref.class_index);
			printf("\t\t- indice de nome: %d\n",
					CP_INFO(i).Fieldref.name_and_type_index);
			break;

		case 0x0a:
			printf("\t\t- Ref. Metodo - tag: %x\n", constant_pool[i].tag);
			printf("\t\t- indice de classe: %d\n",
					CP_INFO(i).Methodref.class_index);
			printf("\t\t- indice de nome: %d\n",
					CP_INFO(i).Methodref.name_and_type_index);
			break;

		case 0x0b:
			printf("\t\t- Ref. Metodo de Interface - tag: %x\n",
					constant_pool[i].tag);
			printf("\t\t- indice de classe: %d\n",
					CP_INFO(i).InterfaceMethodref.class_index);
			printf("\t\t- indice de nome: %d\n",
					CP_INFO(i).InterfaceMethodref.name_and_type_index);
			break;

		case 0x0c:
			printf("\t\t- NameAndType - tag: %x\n", constant_pool[i].tag);
			printf("\t\t- indice de nome: %d\n",
					CP_INFO(i).NameAndType.name_index);
			printf("\t\t- indice de descritor: %d\n",
					CP_INFO(i).NameAndType.descriptor_index);
			break;
		}
	}
	printf("\n--------------------\n\n");
	printf("Access Flags: 0x%02x\n", access_flags);
	printf("\n--------------------\n\n");
	printf("Indice para This Class: %d\n", this_class);
	printf("\n--------------------\n\n");
	printf("Indice para Super Class: %d\n", super_class);
	printf("\n--------------------\n\n");
	printf("Interfaces[%d]:\n", interfaces_count);
	for (i = 0; i < interfaces_count; i++) {
		printf("\t\t+ interfaces[%d]:\n", (i + 1));
		printf("\t\t+ indice: %d:\n", interfaces[i]);
	}

	printf("\n--------------------\n\n");
	printf("Fields[%d]:\n", fields_count);
	for (i = 0; i < fields_count; i++) {
		printf("\t+ fields[%d]:\n", (i + 1));
		printf("\t\t- access flags:%d\n", fields[i].access_flags);
		printf("\t\t- indice de nome:%d\n", fields[i].name_index);
		printf("\t\t- indice de descritor:%d\n", fields[i].descriptor_index);
		printf("\t\t+ attributes[%d]:\n", fields[i].attributes_count);
		for (j = 0; j < fields[i].attributes_count; j++) {
			printf("\t\t+ attribute[%d]:\n", (j + 1));
			print_attr(fields[i].attributes_count, constant_pool,
					&(fields[i].attributes[j]), "\t\t");
		}
	}

	printf("\n--------------------\n\n");
	printf("Methods[%d]:\n", methods_count);
	for (i = 0; i < methods_count; i++) {
		printf("\t+methods[%d]:\n", (i + 1));
		printf("\t\t- access flags:%d\n", methods[i].access_flags);
		printf("\t\t- indice de nome:%d\n", methods[i].name_index);
		printf("\t\t- indice de descritor:%d\n", methods[i].descriptor_index);
		printf("\t\t+ attributes[%d]:\n", methods[i].attributes_count);
		for (j = 0; j < methods[i].attributes_count; j++) {
			printf("\t\t+ attribute[%d]:\n", (j + 1));
			print_attr(methods[i].attributes_count, constant_pool,
					&(methods[i].attributes[j]), "\t\t");
		}
	}

	printf("--\n\n");
	printf("Attributes[%d]:\n", attributes_count);

	for (i = 0; i < attributes_count; i++) {
		printf("\t\tattributes[%d]:\n", (i + 1));
		print_attr(attributes_count, constant_pool, &(attributes[i]), "\t");
	}

	printf("--------------------\n\n");

}

/* Funcao que imprime um campo atributo*/
void print_attr(const u2 size, const cp_info* cp, const attribute_info *attr,
		const char *align) {
	int i;

	if (size == 0) {
		exit(0);
	}

	printf("%s\tindice de nome: %d\n", align, attr->attribute_name_index);
	printf("%s\ttamanho: %d\n", align, attr->attribute_lenght);


	/* Apenas os atributos obrigatorios são imprimidos pela funcao */
	/* Code */
	if (!strcmp((char *) cp[(attr->attribute_name_index) - 1].info.Utf8.bytes,
			"Code")) {
		printf("%s\tAttribute \"%s\":\n", align,
				cp[(attr->attribute_name_index) - 1].info.Utf8.bytes);
		printf("%s\t\tStack depth: %d\n", align, attr->info.Code.max_stack);
		printf("%s\t\tMax Locals: %d\n", align, attr->info.Code.max_locals);
		printf("%s\t\tTamanho de codigo: %d\n", align,
				attr->info.Code.code_lenght);

		bytecode_printer(attr->info.Code.code, attr->info.Code.code_lenght);

		printf("%s\t\ttamanho de tabela de excecoes: %d\n", align,
				attr->info.Code.exception_table_lenght);
		/* atencao */

		for (i = 0; i < (attr->info.Code.exception_table_lenght); i++) {
			printf("%s\t\t\tStart_pc  : %d\n", align,
					attr->info.Code.exception_table[i].start_pc);
			printf("%s\t\t\tEnd_pc    : %d\n", align,
					attr->info.Code.exception_table[i].end_pc);
			printf("%s\t\t\tHandler_pc: %d\n", align,
					attr->info.Code.exception_table[i].handler_pc);
			printf("%s\t\t\tCatch type: %d\n", align,
					attr->info.Code.exception_table[i].catch_type);
		}

		printf("%s\t\tNumero de campos de atributo: %d\n", align,
				attr->info.Code.attributes_count);
		for (i = 0; i < (attr->info.Code.attributes_count); i++) {
			print_attr(attr->info.Code.attributes_count, cp,
					&(attr->info.Code.attributes[i]), "\t\t");
		}
	}
	/* Exceptions */
	else if (!strcmp(
			(char *) cp[(attr->attribute_name_index) - 1].info.Utf8.bytes,
			"Exceptions")) {
		printf("%s\t\tAttribute \"%s\":\n", align,
				cp[(attr->attribute_name_index) - 1].info.Utf8.bytes);
		printf("%s\t\tNumero de excecoes: %d\n", align,
				attr->info.Exceptions.count);
		for (i = 0; i < (attr->info.Exceptions.count); i++) {
			printf("%s\t\t\t%d\n", align, attr->info.Exceptions.index_table[i]);
		}
	}
	/* ConstantValue */
	else if (!strcmp(
			(char *) cp[(attr->attribute_name_index) - 1].info.Utf8.bytes,
			"ConstantValue")) {
		printf("%s\t\tAttribute \"%s\":\n", align,
				cp[(attr->attribute_name_index) - 1].info.Utf8.bytes);
		printf("%s\t\tindice: %d\n", align, attr->info.ConstantValue.index);
	}
	/* Sourcefile */
	else if (!strcmp(
			(char *) cp[(attr->attribute_name_index) - 1].info.Utf8.bytes,
			"SourceFile")) {
		printf("%s\t\tAttribute \"%s\":\n", align,
				cp[(attr->attribute_name_index) - 1].info.Utf8.bytes);
		printf(
				"%s\t\tsourcefile_index: %d <%s>\n",
				align,
				attr->info.Sourcefile.sourcefile_index,
				cp[(attr->info.Sourcefile.sourcefile_index) - 1].info.Utf8.bytes);
	}
	/* Todos os outros atributos */
	else {
		printf("%s\t\tAttribute \"%s\" not yet supported\n", align,
				cp[(attr->attribute_name_index) - 1].info.Utf8.bytes);
	}
}

method_info *get_method(char *method_name) {
	int i, main_name_index;
	/* procura por metodo */
	for (i = 0; i < (int) MAINCLASS->methods_count; i++) {
		main_name_index = MAINCLASS->methods[i].name_index - 1;
		if (!strcmp(
				method_name,
				(char *) MAINCLASS->constant_pool[main_name_index].info.Utf8.bytes)) {
			/*printf("(%s encontrado) \n",method_name);   */
			return &(MAINCLASS->methods[i]);
			break;
		}
	}

	printf("METHOD %s n�o foi encontrado. \n", method_name);
	return 0;
	exit(1);

}

u4 get_method_index(char *method_name) {
	int i, main_name_index;
	/* procura por metodo */
	for (i = 0; i < (int) MAINCLASS->methods_count; i++) {
		main_name_index = MAINCLASS->methods[i].name_index - 1;
		if (!strcmp(
				method_name,
				(char *) MAINCLASS->constant_pool[main_name_index].info.Utf8.bytes)) {
			return i;
			break;
		}
	}


	/*metodo nao encontrado, retorna -1*/
	return -1;
	/*printf("METHOD %s n�o foi encontrado. \n", method_name);
	 exit(1);*/

}
